/**
    ATClab - scriptable Air Traffic Control simulations.
    Copyright (C) 2010 openatclab@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/


#include "engine_impl.h"

#include "aircraft_agent.h"
#include "aircraft_data.h"

#include "airspace.h"
#include "canvas.h"
#include "clock.h"
#include "lab_engine.h"

#include "canvasview.h"
#include "listbox.h"
#include "score.h"
#include "transformation.h"

#include <qdatetime.h>
#include <assert.h>

#include "translator.h" // \todo: kill me!!

using namespace atc;

////////////////////////////////////////////////////////////////////////////////
//
// atc::EngineImpl
//
// todo: this class is to be refactored for Engine Building. The new Engine is 
// todo: LabEngine. 
//
//------------------------------------------------------------------------------
// initalization
//

/*!
 *
 */
EngineImpl::EngineImpl(pact::TrialTaskType a_type)
	  : _atclab		( new LabEngine		)
	  , _airspace	( new Airspace()	)
	  , _canvas		( new Canvas()		)
	  , _clock		( new Clock()		)
	  , _params		( new ParamImpl()	)
	  , _map		( new MapImpl()		)
	  , _sky		( new SkyImpl()		)
	  , _score(0)
	  , m_type(a_type)
{}

/*!
 */
EngineImpl::~EngineImpl() {
	if (m_type != pact::TRIAL_TASK_PRACTICE)
	{
		// Ensure to count the scores for conflict and non-conflicts because they might
		// not occur before the trial ends
		_score->CheckMissedConflicts(false, false);
		_score->CheckMissedNonConflicts(false, false);
	}

	if ( _sky )           delete _sky;
	if ( _map )           delete _map;
	if ( _params )        delete _params;

	// clean up 
	if ( _atclab ) delete _atclab;
	if ( _airspace ) delete _airspace;
	if ( _canvas) delete _canvas;
	if ( _clock) delete _clock;


	if (m_type != pact::TRIAL_TASK_PRACTICE)
	{
		_score->UpdateGoalScores();
	
		emit TrialScore(_score->GetScore());
	}
}

//------------------------------------------------------------------------------
// [new]
//

/*!
 * Build ATC-Lab engine
 */
void EngineImpl::build() {

	process_map(); // initializes transformation - do first
	process_params();
	process_sky();

	_canvas->toggle_srprobe( _params->get_srprobe() );

	_atclab->set_airspace( _airspace );
	_atclab->set_canvas( _canvas );
	_atclab->set_clock( _clock );
}


//------------------------------------------------------------------------------

/*!
 * Provide a reference to Parameters for initialization purposes.
 */
Param& EngineImpl::param(){
	return *_params;
}

/*!
 * Provide a reference to Map for initialization purposes.
 */
Map& EngineImpl::map(){
	return *_map;
}

/*!
 * Provide a reference to Sky for initialization purposes.
 */
Sky& EngineImpl::sky(){
	return *_sky;
}

/*!
 * Initialize the ATC Engine.
 *
 * Note: must process map first since it sets up conversion factors 
 * between map and canvas.
 *
 * pre-condition: the map & sky are already populated.
 */
void EngineImpl::init() {
	build();
}

/*!
 *
 */
void EngineImpl::set_alarm( int ms, QObject *receiver, const char *slot ) {
	_clock->set_alarm( ms, receiver, slot );
}

//--------------------------------------------------------------------------------------------------
// [private] map
// 

/*!
 * Process parameters.
 */
void EngineImpl::process_params() 
{
	// this runs before ownership of these autoptrs is passed to _atclab.

	_clock->set_interval( _params->update_rate() );
	_clock->set_multiple( _params->update_multiplier() );

	_airspace->set_hdoms( _params->hdoms() );
	_airspace->set_vdoms( _params->vdoms() );

	_canvas->set_conflict_colour( _params->conflictColour() );
	_canvas->set_palette( &_params->palette() );

	const Tools tools = _params->tools();
	if ( tools.count(CanvasItem::RTTI_SCALE) ) add_scale_tool( _params->scale_position() );

	if (m_type != pact::TRIAL_TASK_PRACTICE)
	{
        if ( (_score = _params->get_score()) ) // assign _parms->score to _score
		{
			_score->SetConflictDeclarationScoreEnabled(_params->ConflictDeclarationScoreEnabled());
			_score->SetConflictDeclarationAudioEnabled(_params->ConflictDeclarationAudioEnabled());

			connect(_score, SIGNAL(sig_score(long)), 
					_canvas, SIGNAL(sig_update_score(long)));

			connect(_score, SIGNAL(sig_score_quiet(long)), 
					_canvas, SIGNAL(update_score_quietly(long)));

			connect(_airspace, SIGNAL(sig_conflict_detected(const std::string &, const std::string &) ),
					_score, SLOT(conflict(const std::string &, const std::string &)));

			connect(_airspace, SIGNAL(sig_missed_acceptance()),
					_score, SLOT(missed_acceptance()));

			connect(_clock, SIGNAL(seconds(long)), _score, SLOT(UpdateTime(long)));
		}
	}
}

/*!
 */
void EngineImpl::process_map() {

	InfoBlockParamsIt it = _params->_infoBlk.begin();
	for ( ; it != _params->_infoBlk.end(); ++it ) {
		InfoDisplayDetails details;

		switch ( *it ) {
		case TIME_INFO:		
			_canvas->add_time_info();	
			break;

		case SCORE_INFO:	
			_canvas->add_score_info();	
			break;

		case GOAL_INFO:		
			details = _params->GetGoalDisplayDetails();

			_canvas->add_goal_info(details.m_label, details.m_globalRef, details.m_text);	
			break;

		case TOOL_INFO:		
			_canvas->add_tool_info();	
			break;

		case SR_GOAL_INFO:	
			_canvas->AddSelfReferentGoalInfo();	
			break;

		case NORM_GOAL_INFO:
			_canvas->AddNormativeGoalInfo(); 
			break;

		case CORRECT_DECISION_INFO:
			details = _params->GetCorrectDecisionsDisplayDetails();

			_canvas->AddCorrectDecisionsInfo(	details.m_label, 
												details.m_globalRef, 
												details.m_text);
			break;

		case INCORRECT_DECISION_INFO:
			details = _params->GetIncorrectDecisionsDisplayDetails();

			_canvas->AddIncorrectDecisionsInfo(	details.m_label, 
												details.m_globalRef, 
												details.m_text);
			break;
		
		}
	}

	_canvas->set_info_position( _params->_infoPos );

	//
	// set canvas region
	//
	_canvas->resize( _map->rect() );

	//
	// draw canvas map
	//

	process_sectors(_map->sectors());
	process_routes(_map->routes());
	process_locations(_map->locations());

}

/*!
 * Add sectors to canvas
 */
void EngineImpl::process_sectors( const SectorMap &sector_at ) {
	SectorMap::const_iterator asec;
	
	// do active sector first
	asec = sector_at.find( _map->active_sector() );
	_canvas->add_active_sector( asec->second );
	_airspace->define_sector( asec->second );

	// process remaining sectors
	SectorMap::const_iterator sec = sector_at.begin();
	for ( ; sec != sector_at.end(); ++sec ) {
		if ( asec == sec ) continue;
		_canvas->add_sector( sec->second );
		_airspace->add_non_sector( sec->second );
	}

	// define active sector for airspace
}

/*!
 *
 */
void EngineImpl::process_routes( const RouteMap& routes ) {
	RouteMap::const_iterator it = routes.begin();
	for( ; it != routes.end(); ++it){
		_canvas->add_route( *(it->second) );
	}
}

/*!
 */
void EngineImpl::process_locations( const LocationMap &locations ) {
	LocationMap::const_iterator it = locations.begin();
	for( ; it != locations.end(); ++it){
		Location pt = it->second;
		if ( pt.show ) _canvas->add_waypoint( pt.name, pt.x, pt.y );
	}
}

//------------------------------------------------------------------------------
// sky
//

void EngineImpl::process_sky() {
	// aircraft
	AircraftParamMap::const_iterator pit = _sky->aircraft().begin();
	for ( ; pit != _sky->aircraft().end(); ++pit ) {
		process_aircraft(pit->second);
	}

	// weather systems
	AreaHash::const_iterator wit = _sky->weather_systems().begin();
	for ( ; wit != _sky->weather_systems().end(); ++wit ) {
		_canvas->add_weather( wit->second );
	}

	// conflicts
	if ( _score ) {
		_score->zero();
		_score->set_conflicts( _sky->conflicts() );
	}
}

/*!
 * Aircraft builder
 *
 * 
 */
void EngineImpl::process_aircraft( const AircraftParam& aircraft ) {
	AircraftData data;
	AircraftPath path;

	data.call = aircraft.callsign;
	data.type = aircraft.type;

	//
	// process flight path information
	//
	assert( 2 <= aircraft.flightpath.size() );

	FlightPath::const_iterator it = aircraft.flightpath.begin();
	
	const FlightPathDesc *pos0 = &(*it);
	const FlightPathDesc *pos1;
	
	double z0 = aircraft.oAltitude;
	double v0 = aircraft.oVelocity;

	while ( ++it != aircraft.flightpath.end() ) {
		pos1 = &(*it);

		path.push( AircraftPathToken( 
				pos0->x, pos0->y,       z0,       v0
			  ,	pos1->x, pos1->y, pos0->tz, pos0->tv 
		) );

		z0 = pos1->tz;
		v0 = pos1->tv;

		pos0 = pos1;
	}
	
	add_aircraft( data, path, aircraft.launch );
}

//--------------------------------------------------------------------------------------------------
// [public]
//

/*!
 * Add a new aircraft to the system
 *
 * @param data defines the aircraft
 * @param path describes its movement in space
 *
 * @todo: currently this is called from process_aircraft but the method is
 * @todo: public and should be called directly
 */
void EngineImpl::add_aircraft(
		const AircraftData &data
	  , const AircraftPath &path 
	  , unsigned int launch
) {
    AircraftAgent *a = new AircraftAgent( _airspace, data.call.c_str() );
	a->set_data( data );
	a->set_path( path );
	a->SetAcceptanceType(_params->AcceptanceType());

	if ( launch ) {
		a->set_launch( launch );
		_clock->set_alarm( launch, a, SLOT(activate()) );
	}

	//a->set_control( ) // @todo:
	_airspace->add_aircraft( a );
}

/*!
 * Add a scale marker to map (cross hair type)
 */
void EngineImpl::add_scale_tool( GridPosition p ) { 
	_canvas->add_scale( 
			SCALE_X_EXT, SCALE_Y_EXT
		  , SCALE_INTERVAL, SCALE_TICK 
		  , p 
		);
}



//------------------------------------------------------------------------------
// 
//

/*!
QPointArray& EngineImpl::area_points( 
		const AreaDefinition &area_def
	  , QPointArray &pts_ref 
) {
	// validate point array
	if ( ! pts_ref.isNull() ) {
		throw engine_error( "Cannot process area: point array is not empty!" );
	}
	
	// process area's descriptors
	AreaDefinition::const_iterator it = area_def.begin();
	for ( ; it != area_def.end(); ++it) {

		switch ( (*it)->rtti ) {
		
		case ( AreaDescriptor::RTTI_VERTEX ): {
			pts_ref.putPoints( pts_ref.size(), 1, (*it)->x, (*it)->y ); 
			break;
		}
		case ( AreaDescriptor::RTTI_ARC ): {
			ArcDescriptor *arc = static_cast< ArcDescriptor* >( *it );

			QPoint p0( (int)( arc->x ), (int)( arc->y ) );
			int r = (int)( arc->r );

			if ( 0 == pts_ref.size() ) {
				// assume 360 single area arc descriptor
				// @todo: assert( only area descriptor )
				pts_ref.makeEllipse( p0.x() - r, p0.y() - r , 2 * r, 2 * r );
			}
			else {
				++it;  // need next point also to limit arc

				assert( it != area_def.end() );

				QPoint pA( pts_ref.at( pts_ref.size() - 1 ) );
				QPoint pB( (*it)->x, (*it)->y );

				bool minor = true;  // TODO:??

				double bA = bearing(p0, pA);
				double bB = bearing(p0, pB);

				if ( (bB - bA > 180 && minor) || (bB - bA < 180 && !minor) ) {
					double tmp = bA;
					bA = bB;
					bB = tmp;
				}

				double angle = bB - bA; 

				QPointArray arcPts;
				arcPts.makeArc(
					p0.x() - r, p0.y() - r, 2 * r, 2 * r, 
					(floor)(bA * 16 + 0.5), (floor)(angle * 16 + 0.5)
				);

				// Note: do not include pA or pB in final pts.
				pts_ref.putPoints( pts_ref.size() - 1, arcPts.size(), arcPts );
			}			
			
			break;
		}
		case ( AreaDescriptor::RTTI_ELLIPSE ): {
			EllipseDescriptor *e 
				  = static_cast< EllipseDescriptor* >( *it );

			QPoint p0( e->x, e->y );
			int w = (int)( e->w );
			int h = (int)( e->h );
			int a =      ( e->a );

			// @todo: assert only one area descriptor (this)
			pts_ref.makeEllipse( p0.x() - w/2, p0.y() - h/2 , w, h );

			if ( a ) {
				QWMatrix matrix;
				matrix.rotate( -a );
				pts_ref.translate( -p0.x(), -p0.y() );
				pts_ref = matrix.map( pts_ref );
				pts_ref.translate(  p0.x(),  p0.y() );
			}
			
			break;
		}
		default:
			throw engine_error("Unknown area descriptor!");
		}
	}
	return pts_ref;
}
 */


//------------------------------------------------------------------------------

/*!
 * Start/Stop the engine 
 *
 * @todo: remove from builder
 */
void EngineImpl::start() { _atclab->start(); emit sig_engine_resumed(); }
void EngineImpl::stop()  { _atclab->stop(); emit sig_engine_stopped(); }


//------------------------------------------------------------------------------

/*!
 * Creates a view of the Engine's Canvas (Document Object). The View's parent
 * is set so that when the parent goes out of scope the view is automatically
 * cleaned up.
 *
 * Currently returns a QWidget* - but not sure if additional functionality may
 * be required from higher up the inheritance tree?
 */
QWidget* EngineImpl::create_view( QWidget* parent, const char* name ) {
	Q3Canvas * cptr = _canvas; 

	// create new view
	CanvasView *cv = new CanvasView( cptr, parent, name );

	connect( cv, SIGNAL( signal_pause() ), this, SLOT( stop() ) );
	connect( cv, SIGNAL( signal_resume() ), this, SLOT( start() ) );

	connect( this, SIGNAL( sig_engine_stopped() ), cv, SLOT( slot_paused() ) );
	connect( this, SIGNAL( sig_engine_resumed() ), cv, SLOT( slot_resumed() ) );

	cv->set_action_handler( CanvasItem::RTTI_LOCATOR, new RotateCalloutHandler(cv) );

	const Tools select_tools = _params->tools();
	Tools::const_iterator it = select_tools.begin();
	for ( ; it != select_tools.end(); ++it ) 
	{
		AltitudeHandler* altHandler = NULL;
		VelocityHandler* velHandler = NULL;
		CanvasEventHandler* handler = it->second.build->operator()(cv, this);
		bool test = false;

        switch (it->first)
		{
		case CanvasItem::RTTI_ALTBLK:
			if (_params->ConflictDeclarationLevelChange())
			{
				altHandler = static_cast<AltitudeHandler*>(handler);
			
				QObject::connect(_score, SIGNAL(sig_hit()), altHandler, SLOT(resolve()));
			}
			break;
		case CanvasItem::RTTI_VELBLK:
			if (_params->ConflictDeclarationSpeedChange())
			{
				velHandler = static_cast<VelocityHandler*>(handler);
			
				test = QObject::connect(_score, SIGNAL(sig_hit()), velHandler, SLOT(resolve()));
			}
            break;
        default:
            ;
		}

		cv->set_select_handler( it->first, handler);
	}
	
	cv->set_keyed_tools( _params->keyTools(), _params->keyTags() );

	const ToolSet key_tools = _params->toolSet();

	if ( key_tools.count( CanvasItem::BRLINE_TOOL ) ) {
		CanvasEventHandler *handler = new BRLToolHandler(cv);
		cv->set_tool_handler  ( CanvasItem::BRLINE_TOOL,  handler );
		cv->set_action_handler( CanvasItem::RTTI_BRLDATA, handler );
		cv->set_select_handler( CanvasItem::RTTI_BRLDATA, handler );
	}

	if ( key_tools.count( CanvasItem::VECTOR_TOOL ) ) 
			cv->set_tool_handler( CanvasItem::VECTOR_TOOL, new VectorToolHandler(cv) );
	if ( key_tools.count( CanvasItem::PAUSE_TOOL ) ) 
			cv->set_tool_handler( CanvasItem::PAUSE_TOOL, new PauseToolHandler(cv) );
	if ( key_tools.count( CanvasItem::RESUME_TOOL ) ) 
			cv->set_tool_handler( CanvasItem::RESUME_TOOL, new ResumeToolHandler(cv) );

	if ( 
			key_tools.count( CanvasItem::ACCEPT_TOOL  ) ||
			key_tools.count( CanvasItem::HANDOFF_TOOL ) ||
			key_tools.count( CanvasItem::HISTORY_TOOL ) ||
			key_tools.count( CanvasItem::SRPROBE_TOOL ) ||
			key_tools.count( CanvasItem::ROUTE_TOOL   ) 
	)
			cv->set_select_handler( CanvasItem::RTTI_LOCATOR, new ACToolsHandler(cv) );
	

	return cv;
}

long	
EngineImpl::ElapsedTimeSec() const 
{ 
	return _clock->elapsed_time(); 
}

//------------------------------------------------------------------------------
